"""
OKX API 客户端封装
支持现货交易、永续合约交易、市场数据获取等
"""
import os
import time
from typing import List, Dict, Optional, Tuple
from datetime import datetime, timedelta
import requests
import json
from .models import KlineData, Order, Position, AccountBalance, TradeResult
from src.utils.logger import get_logger

logger = get_logger(__name__)


class OKXClient:
    """OKX交易所API客户端"""

    # API端点
    LIVE_URL = "https://www.okx.com"
    SANDBOX_URL = "https://www.okx.com"

    # K线周期映射
    TIMEFRAME_MAP = {
        5: "5m",
        15: "15m",
        60: "1H",
        240: "4H",
        1440: "1D",
    }

    def __init__(self, api_key: str, secret_key: str, passphrase: str,
                 environment: str = "sandbox", simulate: bool = False):
        """
        初始化OKX客户端

        Args:
            api_key: API密钥
            secret_key: 密钥
            passphrase: 密码短语
            environment: 环境 (live/sandbox)
            simulate: 是否使用模拟盘
        """
        self.api_key = api_key
        self.secret_key = secret_key
        self.passphrase = passphrase
        self.environment = environment
        self.simulate = simulate

        # 根据环境选择基础URL
        self.base_url = self.SANDBOX_URL if environment == "sandbox" else self.LIVE_URL

        # 模拟盘模式会添加一个特殊的header
        self.headers = {
            "OK-ACCESS-KEY": api_key,
            "OK-ACCESS-PASSPHRASE": passphrase,
        }
        if simulate:
            self.headers["OK-ACCESS-SIMULATE"] = "1"

        logger.info(f"OKX客户端初始化: 环境={environment}, 模拟盘={simulate}")

    def _request(self, method: str, endpoint: str, params: Dict = None,
                 data: Dict = None) -> Dict:
        """发送API请求"""
        url = f"{self.base_url}/api/v5/{endpoint}"

        try:
            if method == "GET":
                response = requests.get(url, params=params, headers=self.headers, timeout=30)
            elif method == "POST":
                response = requests.post(url, json=data, headers=self.headers, timeout=30)
            else:
                raise ValueError(f"不支持的HTTP方法: {method}")

            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            logger.error(f"API请求失败: {endpoint}, 错误: {str(e)}")
            return {"code": "500", "msg": str(e), "data": []}

    # ==================== 市场数据接口 ====================

    def get_klines(self, symbol: str, timeframe: int, limit: int = 100) -> List[KlineData]:
        """
        获取K线数据 (最新的K线)

        Args:
            symbol: 交易对 (如 BTC-USDT)
            timeframe: 时间框架 (分钟数: 5, 15, 60, 240等)
            limit: 获取的K线数量

        Returns:
            K线数据列表
        """
        tf = self.TIMEFRAME_MAP.get(timeframe)
        if not tf:
            logger.error(f"不支持的时间框架: {timeframe}")
            return []

        endpoint = "market/candles"
        params = {
            "instId": symbol,
            "bar": tf,
            "limit": min(limit, 300),  # OKX最多返回300条
        }

        try:
            result = self._request("GET", endpoint, params=params)
            if result.get("code") != "0":
                logger.error(f"获取K线失败: {result.get('msg')}")
                return []

            klines = []
            for item in result.get("data", []):
                kline = KlineData(
                    timestamp=int(item[0]),
                    open=float(item[1]),
                    high=float(item[2]),
                    low=float(item[3]),
                    close=float(item[4]),
                    volume=float(item[5]),
                    quote_volume=float(item[7])
                )
                klines.append(kline)

            # 按时间戳升序排列
            klines.sort(key=lambda x: x.timestamp)
            logger.info(f"获取K线成功: {symbol} {timeframe}分钟, 共{len(klines)}条")
            return klines
        except Exception as e:
            logger.error(f"解析K线数据异常: {str(e)}")
            return []

    def get_historical_klines(self, symbol: str, timeframe: int,
                             start_date: datetime, end_date: datetime) -> List[KlineData]:
        """
        获取历史K线数据 (用于回测) - 使用 /market/history-candles 端点支持分页

        Args:
            symbol: 交易对 (如 BTC-USDT)
            timeframe: 时间框架 (分钟数: 5, 15, 60, 240等)
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            K线数据列表 (按时间戳升序)
        """
        tf = self.TIMEFRAME_MAP.get(timeframe)
        if not tf:
            logger.error(f"不支持的时间框架: {timeframe}")
            return []

        start_ts = int(start_date.timestamp() * 1000)
        end_ts = int(end_date.timestamp() * 1000)

        all_klines = []
        after = end_ts
        request_count = 0
        max_requests = 200

        logger.info(f"开始获取历史K线: {symbol} {timeframe}分钟 ({start_date} - {end_date})")

        try:
            endpoint = "market/history-candles"

            while request_count < max_requests and after > start_ts:
                request_count += 1

                params = {
                    "instId": symbol,
                    "bar": tf,
                    "limit": 300,  # 每次最多300条
                }

                if after is not None:
                    params["after"] = str(after)

                result = self._request("GET", endpoint, params=params)

                if result.get("code") != "0":
                    logger.warning(f"获取K线失败 (请求 {request_count}): {result.get('msg')}")
                    break

                data = result.get("data", [])
                if not data:
                    logger.info(f"获取K线完成: 无更多数据")
                    break

                # 解析K线数据
                for item in data:
                    kline = KlineData(
                        timestamp=int(item[0]),
                        open=float(item[1]),
                        high=float(item[2]),
                        low=float(item[3]),
                        close=float(item[4]),
                        volume=float(item[5]),
                        quote_volume=float(item[7])
                    )
                    all_klines.append(kline)

                # 更新 after 用于下一次请求
                if data:
                    after = int(data[-1][0]) - 1
                    oldest_time = datetime.fromtimestamp(after / 1000)
                    logger.info(f"已获取 {len(data)} 条K线 (请求 {request_count}), "
                              f"时间范围: {oldest_time} - 当前, 已累计 {len(all_klines)} 条")

                if len(data) < 300:
                    logger.info(f"数据少于300条，已到达历史数据末端")
                    break

                time.sleep(0.1)  # 避免API限流

            # 按时间戳升序排列，并过滤到指定时间范围
            all_klines.sort(key=lambda x: x.timestamp)
            filtered_klines = [k for k in all_klines if start_ts <= k.timestamp <= end_ts]

            logger.info(f"获取历史K线完成: {symbol} {timeframe}分钟, "
                       f"原始{len(all_klines)}条, 过滤后{len(filtered_klines)}条, "
                       f"请求数: {request_count}")

            return filtered_klines

        except Exception as e:
            logger.error(f"获取历史K线异常: {str(e)}")
            return []

    def get_ticker(self, symbol: str) -> Optional[Dict]:
        """获取最新的行情数据"""
        endpoint = "market/ticker"
        params = {"instId": symbol}

        result = self._request("GET", endpoint, params=params)
        if result.get("code") != "0":
            return None

        data = result.get("data", [{}])[0]
        return {
            "symbol": symbol,
            "last_price": float(data.get("last", 0)),
            "bid": float(data.get("bidPx", 0)),
            "ask": float(data.get("askPx", 0)),
            "volume_24h": float(data.get("vol24h", 0)),
            "change_24h": float(data.get("change24h", 0)),
            "timestamp": int(data.get("ts", 0))
        }

    # ==================== 交易接口 ====================

    def place_order(self, symbol: str, side: str, order_type: str,
                    quantity: float, price: Optional[float] = None,
                    trade_mode: str = "isolated") -> TradeResult:
        """
        下单

        Args:
            symbol: 交易对
            side: 交易方向 (buy/sell)
            order_type: 订单类型 (market/limit)
            quantity: 交易数量
            price: 价格 (限价单需要)
            trade_mode: 交易模式 (isolated/cross)

        Returns:
            交易结果
        """
        endpoint = "trade/order"

        data = {
            "instId": symbol,
            "tdMode": trade_mode,
            "side": side,
            "ordType": order_type,
            "sz": str(quantity),
        }

        if order_type == "limit" and price:
            data["px"] = str(price)

        result = self._request("POST", endpoint, data=data)

        if result.get("code") != "0":
            logger.error(f"下单失败: {result.get('msg')}")
            return TradeResult(success=False, message=result.get("msg", "未知错误"))

        data = result.get("data", [{}])[0]
        order_id = data.get("ordId")

        logger.info(f"订单下单成功: {symbol}, 方向={side}, 数量={quantity}, 订单ID={order_id}")

        return TradeResult(
            success=True,
            order_id=order_id,
            timestamp=int(result.get("ts", 0))
        )

    def cancel_order(self, symbol: str, order_id: str) -> TradeResult:
        """取消订单"""
        endpoint = "trade/cancel-order"

        data = {
            "instId": symbol,
            "ordId": order_id,
        }

        result = self._request("POST", endpoint, data=data)

        if result.get("code") != "0":
            logger.error(f"取消订单失败: {result.get('msg')}")
            return TradeResult(success=False, message=result.get("msg"))

        logger.info(f"订单取消成功: {order_id}")
        return TradeResult(success=True, order_id=order_id)

    def get_order(self, symbol: str, order_id: str) -> Optional[Order]:
        """获取订单详情"""
        endpoint = "trade/order"
        params = {
            "instId": symbol,
            "ordId": order_id,
        }

        result = self._request("GET", endpoint, params=params)
        if result.get("code") != "0":
            return None

        data = result.get("data", [{}])[0]

        order = Order(
            order_id=data.get("ordId"),
            symbol=symbol,
            order_type=data.get("ordType"),
            side=data.get("side"),
            price=float(data.get("px", 0)),
            quantity=float(data.get("sz", 0)),
            status=data.get("state"),
            filled_qty=float(data.get("accFillSz", 0)),
            filled_price=float(data.get("avgPx", 0)),
            timestamp=int(data.get("ts", 0))
        )

        return order

    # ==================== 账户接口 ====================

    def get_balance(self, currency: str = None) -> List[AccountBalance]:
        """
        获取账户余额

        Args:
            currency: 币种 (不指定则返回全部)

        Returns:
            余额列表
        """
        endpoint = "account/balance"

        result = self._request("GET", endpoint)
        if result.get("code") != "0":
            logger.error(f"获取余额失败: {result.get('msg')}")
            return []

        balances = []
        data = result.get("data", [{}])[0]

        for detail in data.get("details", []):
            ccy = detail.get("ccy")
            if currency and ccy != currency:
                continue

            balance = AccountBalance(
                currency=ccy,
                balance=float(detail.get("bal", 0)),
                available=float(detail.get("availBal", 0)),
                frozen=float(detail.get("frozenBal", 0))
            )
            balances.append(balance)

        return balances

    def get_positions(self, symbol: str = None) -> List[Position]:
        """
        获取持仓

        Args:
            symbol: 交易对 (不指定则返回全部)

        Returns:
            持仓列表
        """
        endpoint = "account/positions"
        params = {}

        if symbol:
            params["instId"] = symbol

        result = self._request("GET", endpoint, params=params)
        if result.get("code") != "0":
            logger.error(f"获取持仓失败: {result.get('msg')}")
            return []

        positions = []
        for data in result.get("data", []):
            position = Position(
                symbol=data.get("instId"),
                side="long" if data.get("posSide") == "long" else "short",
                quantity=float(data.get("pos", 0)),
                avg_price=float(data.get("avgPx", 0)),
                current_price=float(data.get("markPx", 0)),
                unrealized_pnl=float(data.get("upl", 0)),
                leverage=float(data.get("lever", 1.0))
            )
            positions.append(position)

        return positions

    def set_leverage(self, leverage: float, symbol: str = None) -> bool:
        """
        设置杠杆倍数

        Args:
            leverage: 杠杆倍数
            symbol: 交易对

        Returns:
            是否成功
        """
        endpoint = "account/set-leverage"

        data = {
            "lever": str(leverage),
            "mgnMode": "isolated",  # 逐仓
        }

        if symbol:
            data["instId"] = symbol

        result = self._request("POST", endpoint, data=data)
        success = result.get("code") == "0"

        if success:
            logger.info(f"杠杆设置成功: {leverage}x")
        else:
            logger.error(f"杠杆设置失败: {result.get('msg')}")

        return success

    # ==================== 行情数据接口 ====================

    def get_funding_rate(self, symbol: str) -> Optional[Dict]:
        """获取永续合约资金费率"""
        endpoint = "public/funding-rate"
        params = {"instId": symbol}

        result = self._request("GET", endpoint, params=params)
        if result.get("code") != "0":
            return None

        data = result.get("data", [{}])[0]
        return {
            "symbol": symbol,
            "funding_rate": float(data.get("fundingRate", 0)),
            "next_funding_time": int(data.get("nextFundingTime", 0)),
            "timestamp": int(data.get("ts", 0))
        }
